package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"maps"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"unicode"

	"github.com/duke-git/lancet/strutil"
	set "github.com/duke-git/lancet/v2/datastructure/set"

	"golang.org/x/tools/go/packages"
)

// -------------------------------
// 1. 中间数据结构 (IR)
// -------------------------------
type API struct {
	Package    string      // 包路径（import path）
	StructName string      // 控制器结构体名
	Methods    []APIMethod // 解析出来的公开方法
}

type TSInterfaceDef string

type APIMethod struct {
	Name       string  // 方法名
	Params     []Field // 参数列表
	Returns    []Field // 返回值列表
	HasError   bool    // 是否返回 error
	HTTPMethod string  // 映射的 HTTP 方法 (默认 POST, 以后可扩展)
	Route      string  // API 路由 (默认 /api/方法名)
}

type Field struct {
	Name string
	Type string

	IsBuiltin bool
	Package   string

	TSType string
}

// -------------------------------
// 2. 解析源码 -> API 中间数据
// -------------------------------
func parsePackage(packagePath string) ([]API, map[string]string, error) {
	var apis []API
	files, err := os.ReadDir(packagePath)
	if err != nil {
		return nil, nil, err
	}

	re := regexp.MustCompile(`\/\/\s@Controller`)
	tsTypeDefs := make(map[string]string)

	for _, file := range files {
		if file.IsDir() {
			continue
		}
		path := filepath.Join(packagePath, file.Name())
		fset := token.NewFileSet()
		root, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
		if err != nil {
			return nil, nil, err
		}

		dir := filepath.Dir(path)
		if !strings.Contains(dir, ".") {
			dir = "./" + dir
		}
		pkgPath, err := getPkgPath(dir)
		if err != nil {
			return nil, nil, err
		}

		importMap := make(map[string]string)
		for _, imp := range root.Imports {
			path := strings.Trim(imp.Path.Value, `"`)
			name := ""
			if imp.Name != nil {
				name = imp.Name.Name
			} else {
				parts := strings.Split(path, "/")
				name = parts[len(parts)-1]
			}
			importMap[name] = imp.Path.Value
		}

		// [DEBUG]
		// ast.Print(fset, root)

		// 找 @Controller 标记的 struct
		ast.Inspect(root, func(node ast.Node) bool {
			switch x := node.(type) {
			case *ast.GenDecl:
				if x.Doc == nil {
					return true
				}
				for _, comment := range x.Doc.List {
					if re.MatchString(comment.Text) {
						if len(x.Specs) == 1 {
							if typeSpec, ok := x.Specs[0].(*ast.TypeSpec); ok {
								if unicode.IsUpper(rune(typeSpec.Name.Name[0])) {
									api := API{
										Package:    pkgPath,
										StructName: typeSpec.Name.Name,
									}
									apis = append(apis, api)
								}
							}
						} else {
							err = fmt.Errorf("multiple struct in one controller")
							return false
						}
					}
				}
			case *ast.FuncDecl:
				if x.Recv == nil || !unicode.IsUpper(rune(x.Name.Name[0])) {
					return true
				}
				structName := recvStructName(x.Recv.List[0].Type)

				// 找到对应 API
				for i := range apis {
					if apis[i].StructName == structName {
						// 处理@Ignore
						if x.Doc != nil && strings.Contains(x.Doc.Text(), "@Ignore") {
							return true
						}

						var tsTypes map[string]string
						var method APIMethod
						method, tsTypes, err = parseFuncDecl(importMap, pkgPath, x)
						if err != nil {
							return false
						}
						apis[i].Methods = append(apis[i].Methods, method)
						maps.Copy(tsTypeDefs, tsTypes)
					}
				}
			}
			return true
		})

		if err != nil {
			return nil, nil, err
		}
	}

	return apis, tsTypeDefs, nil
}

func recvStructName(expr ast.Expr) string {
	switch t := expr.(type) {
	case *ast.StarExpr:
		if ident, ok := t.X.(*ast.Ident); ok {
			return ident.Name
		}
	case *ast.Ident:
		return t.Name
	}
	return ""
}

func parseFuncDecl(importMap map[string]string, currentPkg string, fn *ast.FuncDecl) (APIMethod, map[string]string, error) {
	tsTypeDefs := make(map[string]string)
	// fmt.Println(fn.Name.Name + "========> params")
	params, oneTsTypeDefs := parseFields(importMap, currentPkg, fn.Type.Params, "param")
	for _, param := range params {
		if param.Type == "error" {
			return APIMethod{}, nil, errors.New("error type param is not supported")
		}
	}
	maps.Copy(tsTypeDefs, oneTsTypeDefs)

	// fmt.Println(fn.Name.Name + "========> returns")
	returns, onetsTypeDefs := parseFields(importMap, currentPkg, fn.Type.Results, "ret")
	maps.Copy(tsTypeDefs, onetsTypeDefs)

	method := APIMethod{
		Name:       fn.Name.Name,
		Params:     params,
		Returns:    returns,
		HTTPMethod: "POST",
		Route:      "/" + fn.Name.Name,
	}

	// 判断是否有 error 返回
	for _, r := range method.Returns {
		if r.Type == "error" {
			method.HasError = true
		}
	}
	return method, tsTypeDefs, nil
}

func parseFields(importMap map[string]string, currentPkg string, fl *ast.FieldList, prefix string) ([]Field, map[string]string) {
	if fl == nil {
		return nil, nil
	}

	var fields []Field
	tsTypes := make(map[string]string)
	for i, f := range fl.List {
		field, oneTsTypes := parseField(importMap, currentPkg, f, prefix, i)
		fields = append(fields, field...)
		maps.Copy(tsTypes, oneTsTypes)
	}
	return fields, tsTypes
}

func parseField(importMap map[string]string, currentPkg string, field *ast.Field, prefix string, i int) ([]Field, map[string]string) {
	fields := make([]Field, 0)
	names := field.Names
	if len(names) == 0 {
		names = []*ast.Ident{{Name: fmt.Sprintf("%s%d", prefix, i)}}
	}

	tsTypes := make(map[string]string)

	for _, n := range names {
		pkgPath, typeName, fullType := resolveType(field.Type, importMap, currentPkg)
		tsType, oneTsTypes := resolveTsType(field.Type, importMap, currentPkg)
		fields = append(fields, Field{
			Name:    n.Name,
			Type:    fullType,
			Package: pkgPath,

			IsBuiltin: isBuiltin(typeName),
			TSType:    tsType,
		})
		maps.Copy(tsTypes, oneTsTypes)
	}

	return fields, tsTypes
}

// resolveType: 根据 ast.Expr 和 importMap 解析类型
// 返回：包路径、类型名、完整类型字符串
func resolveType(expr ast.Expr, importMap map[string]string, currentPkg string) (pkgPath, typeName, fullType string) {
	switch t := expr.(type) {
	case *ast.Ident:
		// typeName = t.Name
		// fullType = t.Name
		typeName = t.Name
		fullType = t.Name
		if _, ok := supportTypes[typeName]; ok {
			// 基础类型
			return "", typeName, fullType
		}
		// 当前包内自定义类型
		sel := t.Name
		x := strutil.AfterLast(currentPkg, "/")
		return "\"" + currentPkg + "\"", typeName, x + "." + sel

	case *ast.StarExpr:
		pkgPath, typeName, inner := resolveType(t.X, importMap, currentPkg)
		return pkgPath, typeName, "*" + inner

	case *ast.SelectorExpr:
		// SelectorExpr: X.Sel
		if id, ok := t.X.(*ast.Ident); ok {
			typeName = t.Sel.Name
			if full, ok := importMap[id.Name]; ok {
				return full, typeName, id.Name + "." + typeName
			}
			// fallback: id.Name 当作包名
			return id.Name, typeName, id.Name + "." + typeName
		}
		// 嵌套 SelectorExpr
		pkg, tn, inner := resolveType(t.X, importMap, currentPkg)
		_ = tn
		return pkg, t.Sel.Name, inner + "." + t.Sel.Name

	case *ast.ArrayType:
		pkgPath, typeName, inner := resolveType(t.Elt, importMap, currentPkg)
		return pkgPath, typeName, "[]" + inner

	case *ast.Ellipsis:
		_, typeName, inner := resolveType(t.Elt, importMap, currentPkg)
		return "", typeName, "..." + inner

	case *ast.MapType:
		_, keyName, keyStr := resolveType(t.Key, importMap, currentPkg)
		_, valName, valStr := resolveType(t.Value, importMap, currentPkg)
		_, _ = keyName, valName
		return "", "", "map[" + keyStr + "]" + valStr

	default:
		// 其他类型直接返回类型表达式的 Go 类型字符串
		return "", fmt.Sprintf("%T", expr), fmt.Sprintf("%T", expr)
	}
}

// FIXME: TIME 类型还有问题
// 数组还有问题
func resolveTsType(goType ast.Expr, importMap map[string]string, currentPkg string) (string, map[string]string) {
	tsTypeDefs := make(map[string]string)
	switch t := goType.(type) {
	case *ast.Ident:
		switch t.Name {
		case "int", "int8", "int16", "int32", "int64":
			return "number", tsTypeDefs
		case "uint", "uint8", "uint16", "uint32", "uint64":
			return "number", tsTypeDefs
		case "flaot32", "float64":
			return "number", tsTypeDefs
		case "string":
			return "string", tsTypeDefs
		case "bool":
			return "boolean", tsTypeDefs
		case "error":
			return "error", tsTypeDefs
		default:
			oneTsTypeDefs, err := convertTsInterface(currentPkg, t.Name, currentPkg)
			if err != nil {
				panic(err)
			}
			maps.Copy(tsTypeDefs, oneTsTypeDefs)
			return t.Name, tsTypeDefs
		}
	case *ast.ArrayType:
		elemType, oneTsTypes := resolveTsType(t.Elt, importMap, currentPkg)
		maps.Copy(tsTypeDefs, oneTsTypes)
		return "Array<" + elemType + ">", tsTypeDefs
	case *ast.MapType:
		// return "{ [key: " + goToTsType(t.Key, "") + "]: " + goToTsType(t.Value, "") + " }"
		return resolveMapType(t, importMap, currentPkg), tsTypeDefs
	case *ast.StarExpr:
		elemType, oneTsTypes := resolveTsType(t.X, importMap, currentPkg)
		maps.Copy(tsTypeDefs, oneTsTypes)
		return elemType + " | null", tsTypeDefs // Pointer type
	case *ast.SelectorExpr:
		// SelectorExpr: X.Sel
		typeName := ""
		if id, ok := t.X.(*ast.Ident); ok {
			// time.Time 处理
			if id.Name == "time" && t.Sel.Name == "Time" {
				return "string", tsTypeDefs
			}

			typeName = t.Sel.Name
			if full, ok := importMap[id.Name]; ok {
				oneTsTypes, err := convertTsInterface(full, typeName, currentPkg)
				if err != nil {
					panic(err)
				}
				maps.Copy(tsTypeDefs, oneTsTypes)

				return typeName, tsTypeDefs
			}
			// fallback: id.Name 当作包名
			// return id.Name, typeName, id.Name + "." + typeName
			return typeName, tsTypeDefs
		}
		// 嵌套 SelectorExpr
		tsType, oneTsTypeDefs := resolveTsType(t.X, importMap, currentPkg)
		maps.Copy(tsTypeDefs, oneTsTypeDefs)
		return tsType, tsTypeDefs
	default:
		return "any", tsTypeDefs
	}
}

// canvertTsInterface
func convertTsInterface(fullPackage string, structName string, currentPkg string) (map[string]string, error) {
	typeSpec, importMap, err := resolveStruct(fullPackage, structName)
	if err != nil {
		return nil, err
	}

	// 转换TSInterface
	tsTypes, err := parseTypeSepcToTsInterface(typeSpec, structName, importMap, currentPkg)
	if err != nil {
		return nil, err
	}

	return tsTypes, nil
}

// parseTypeSepcToTsInterface
// type interface
func parseTypeSepcToTsInterface(structType *ast.StructType, structName string, importMap map[string]string, currentPkg string) (map[string]string, error) {
	ret := make(map[string]string, 0)

	tsInterfaceDef := strings.Builder{}
	fmt.Fprintf(&tsInterfaceDef, "export interface %s { \n", structName)

	// parseField(importMap map[string]string, currentPkg string, field *ast.Field, prefix string, i int)
	for _, field := range structType.Fields.List {
		if len(field.Names) == 0 {
			return nil, fmt.Errorf("struct %s field name is empty", structName)
		}
		fieldName := field.Names[0].Name
		if field.Tag != nil {
			// try to parse field json tag
			tag := field.Tag.Value
			tag = strings.TrimPrefix(tag, "`")
			parsed := reflect.StructTag(tag)

			jsonTag := parsed.Get("json")
			if jsonTag != "" {
				fieldName = strings.Split(jsonTag, ",")[0]
			}
		}
		tsType, oneTsTypeDefs := resolveTsType(field.Type, importMap, currentPkg)
		maps.Copy(ret, oneTsTypeDefs)

		fmt.Fprintf(&tsInterfaceDef, "  %s: %s;\n", fieldName, tsType)
	}

	tsInterfaceDef.WriteString("}\n")

	ret[structName] = tsInterfaceDef.String()

	return ret, nil
}

func resolveMapType(mapType *ast.MapType, importMap map[string]string, currentPkg string) string {
	keyType, _ := resolveTsType(mapType.Key, importMap, currentPkg)
	valueType, _ := resolveTsType(mapType.Value, importMap, currentPkg)

	// Only allow string and number as key types
	switch keyType {
	case "string", "number":
		return fmt.Sprintf("{ [key: %s]: %s }", keyType, valueType)
	default:
		log.Fatalf("Unsupported map key type: %s. Only string or number are allowed.", keyType)
		return "unknown"
	}
}

func getPkgPath(pattern string) (string, error) {
	cfg := &packages.Config{Mode: packages.NeedName | packages.NeedFiles}
	pkgs, err := packages.Load(cfg, pattern)
	if err != nil {
		return "", err
	}
	if len(pkgs) > 0 {
		return pkgs[0].PkgPath, nil
	}
	return "", nil
}

// -------------------------------
// 3. 生成 Go router 代码
// -------------------------------
func generateRouter(apis []API) string {
	var sb strings.Builder

	// header
	sb.WriteString(`// Code generated by generator; DO NOT EDIT.
//
//go:generate go run ./generate/
package main

`)

	// import

	importSet := set.New("\"github.com/gin-gonic/gin\"")
	for _, api := range apis {
		importSet.Add("\"" + api.Package + "\"")
	}
	// import params pkg
	for _, api := range apis {
		for _, m := range api.Methods {
			for _, p := range m.Params {
				if p.Package != "" {
					importSet.Add(p.Package)
				}
			}
		}
	}
	sb.WriteString("import (\n")
	sb.WriteString(strings.Join(importSet.ToSlice(), "\n"))
	sb.WriteString("\n)\n\n")

	// initRouter
	sb.WriteString("func initRouter(r *gin.Engine,\n")
	for _, api := range apis {
		fmt.Fprintf(&sb, "\t%s *%s.%s,\n", strutil.LowerFirst(api.StructName), filepath.Base(api.Package), api.StructName)
	}
	sb.WriteString("){\n")

	// routerWrapper
	for _, api := range apis {
		fmt.Fprintf(&sb, `
	%sWrapper := %sWrapper{
		%s: %s,
	}
`, strings.ToLower(api.StructName), api.StructName, api.StructName, strutil.LowerFirst(api.StructName))

		for _, m := range api.Methods {
			fmt.Fprintf(&sb, "\tr.%s(\"/api/%s%s\", %sWrapper.%s)\n",
				m.HTTPMethod, strutil.LowerFirst(api.StructName), m.Route, strings.ToLower(api.StructName), m.Name)
		}
	}

	sb.WriteString("}\n\n")

	// wrapper structs
	for _, api := range apis {
		fmt.Fprintf(&sb, "type %sWrapper struct { *%s.%s }\n\n",
			api.StructName, filepath.Base(api.Package), api.StructName)

		// wrapper methods
		for _, m := range api.Methods {
			fmt.Fprintf(&sb, "func (r %sWrapper) %s(ctx *gin.Context) {\n", api.StructName, m.Name)

			paramsSnippet := ""
			returnsSnippet := ""

			// param
			if len(m.Params) > 0 {
				fmt.Fprintf(&sb, "\ttype request struct {\n")
				for _, p := range m.Params {
					fmt.Fprintf(&sb, "\t\t%s %s `json:\"%s\"`\n", strutil.UpperFirst(p.Name), p.Type, p.Name)
					paramsSnippet += fmt.Sprintf("req.%s, ", strutil.UpperFirst(p.Name))
				}
				fmt.Fprintf(&sb, "\t}\n\n")
				fmt.Fprintf(&sb, "\tvar req request\n")

				fmt.Fprintf(&sb, "\tif err := ctx.ShouldBindJSON(&req); err != nil {\n")
				fmt.Fprintf(&sb, "\t\tctx.JSON(400, gin.H{\"error\": err.Error()})\n")
				fmt.Fprintf(&sb, "\t\treturn\n")
				fmt.Fprintf(&sb, "\t}\n\n")

			}
			paramsSnippet = strings.TrimSuffix(paramsSnippet, ", ")

			// return
			errName := ""
			if len(m.Returns) > 0 {
				for _, p := range m.Returns {
					returnsSnippet += fmt.Sprintf("%s, ", p.Name)
					if (p.Type == "error" && errName == "") || (p.Type == "error" && p.Name == "err") {
						errName = p.Name
					}
				}
			}
			returnsSnippet = strings.TrimSuffix(returnsSnippet, ", ")

			// call
			if len(m.Returns) > 0 {
				fmt.Fprintf(&sb, "\t%s := r.%s.%s(%s)\n", returnsSnippet, api.StructName, m.Name, paramsSnippet)
			} else {
				fmt.Fprintf(&sb, "\tr.%s.%s(%s)\n", api.StructName, m.Name, paramsSnippet)
			}

			if m.HasError {
				fmt.Fprintf(&sb, "\tif %s != nil {\n", errName)
				fmt.Fprintf(&sb, "\t\tctx.JSON(500, gin.H{\"error\": %s.Error()})\n", errName)
				sb.WriteString("\t\treturn\n")
				sb.WriteString("\t}\n")
			}

			// res
			errorCount := 0
			fmt.Fprintf(&sb, "\n\tres := make(map[string]any)\n")
			for _, p := range m.Returns {
				if p.Type == "error" {
					errorCount++
					continue
				}
				fmt.Fprintf(&sb, "\tres[\"%s\"] = %s\n", p.Name, p.Name)
			}
			fmt.Fprintf(&sb, "\n\tctx.JSON(200, res)\n}\n\n")

			if errorCount > 1 {
				fmt.Println("[warning] method " + m.Name + " has more than one error return")
			}
		}

	}

	return sb.String()
}

func generateTSClient(apis []API, tsTypes map[string]string) (map[string]string, string) {
	tsClientFile := make(map[string]string)
	types := strings.Builder{}

	for _, api := range apis {
		sb := strings.Builder{}

		sb.WriteString(`import client from "./client";`)
		sb.WriteString("\n")
		sb.WriteString(`import * as types from "./types";`)
		sb.WriteString("\n\n")

		for _, m := range api.Methods {
			// export api
			fmt.Fprintf(&sb, "export const %s = async (params: {", strutil.LowerFirst(m.Name))
			if len(m.Params) > 0 {
				fmt.Fprintf(&sb, "\n")
			}
			for _, p := range m.Params {
				if p.IsBuiltin {
					fmt.Fprintf(&sb, "  %s: %s;\n", strutil.LowerFirst(p.Name), p.TSType)
				} else {
					fmt.Fprintf(&sb, "  %s: types.%s;\n", strutil.LowerFirst(p.Name), p.TSType)
				}
			}
			sb.WriteString("}) => {\n")
			fmt.Fprintf(&sb, `	const res = await client.%s<types.%s>("/api/%s%s", params)`, strings.ToLower(m.HTTPMethod), m.Name, strutil.LowerFirst(api.StructName), m.Route)
			sb.WriteString("\n  return res.data\n")
			sb.WriteString("}\n\n")

			// generate response type
			respType := strings.Builder{}
			respType.WriteString("export interface ")
			respType.WriteString(m.Name)
			respType.WriteString(" {\n")
			for _, p := range m.Returns {
				if p.Type == "error" {
					continue
				}
				respType.WriteString(fmt.Sprintf("  %s: %s;\n", strutil.LowerFirst(p.Name), p.TSType))
			}
			respType.WriteString("}\n\n")
			types.WriteString(respType.String())
		}

		tsClientFile[strutil.LowerFirst(api.StructName)] = sb.String()
	}

	// export types
	for _, tsType := range tsTypes {
		types.WriteString(tsType)
		types.WriteString("\n")
	}

	return tsClientFile, types.String()
}

// -------------------------------
// 4. 入口
// -------------------------------
func main() {
	apis, tsTypes, err := parsePackage("backend")
	if err != nil {
		panic(err)
	}

	// 1) 生成 Go router
	code := generateRouter(apis)
	if err := os.WriteFile("router.gen.go", []byte(code), 0o644); err != nil {
		panic(err)
	}

	// 2) 基于 apis 生成 TS client
	// const frontDir = "./packages/front/src/lib/api/"
	const frontDir = "./packages/sdk/lib/api/"
	tsCode, types := generateTSClient(apis, tsTypes)
	for filename, code := range tsCode {
		err := os.WriteFile(frontDir+filename+".ts", []byte(code), 0o644)
		if err != nil {
			panic(err)
		}
	}
	err = os.WriteFile(frontDir+"types.ts", []byte(types), 0o644)
	if err != nil {
		panic(err)
	}
}
